package cn.edu.uestc.indoorlocation.algorithm.knn;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import cn.edu.uestc.indoorlocation.algorithm.Location;
import cn.edu.uestc.indoorlocation.algorithm.common.CommonUtils;
import cn.edu.uestc.indoorlocation.algorithm.common.MathUtils;
import cn.edu.uestc.indoorlocation.algorithm.common.Sensor;
import cn.edu.uestc.indoorlocation.algorithm.kalman.EKF;
import cn.edu.uestc.indoorlocation.dao.LocationDataSource;
import cn.edu.uestc.indoorlocation.dao.model.Point;
import cn.edu.uestc.indoorlocation.dao.model.Print;
import cn.edu.uestc.indoorlocation.dao.model.Rss;

/**
 * KNN算法实现
 * @author vincent
 *
 */

public class KNNLocation implements Location{

	/**
	 * 数据源，要么静态数据源，要么数据库源
	 */
	private LocationDataSource dataSource;
	/**
	 * 距离或相似度方法:
	 * euclidean: 欧式距离
	 * cosine   ： 余弦相似度
	 */
	private final String distanceMethod;
	/**
	 * KNN的K值
	 */
	private int k;
	
	/**
	 * 缓存实现，数据库指纹数据缓存
	 * 因为有多线程竞争问题，所以使用线程安全的ConcurrentHashMap
	 */
	public Map<Point, Print> cache = new ConcurrentHashMap<>(100);
	
	/**
	 * 卡尔曼滤波
	 */
	private EKF kalman;
	
	
	public void setDataSource(LocationDataSource source) {
		this.dataSource = source;
	}

	/**
	 * @param method 距离算法：如欧式距离或余弦相似度
	 * @param k  KNN的k值
	 */
	public KNNLocation(String method, int k) {
		this.distanceMethod = method;
		this.k = k;
		this.kalman = new EKF();
	}
	
	@Override
	public Point predict(JSONObject json) {
		
		List<Rss> rssis = new ArrayList<>();
		List<Sensor> sensors = new ArrayList<>();
		JSONArray array = json.getJSONArray("rssis");
		
		int len = array.size();
		//取得json中的信号值
		for (int i = 0; i < len; i++) {
			JSONObject obj = array.getJSONObject(i);
			Rss rss = new Rss(obj.getString("MAC"), obj.getIntValue("RSS"));
			rssis.add(rss);
		}
		
		JSONArray sensorArray = json.getJSONArray("sensor");
		
		///TODO  取得传感器值
//		sensor = new Sensor.Builder().accelerationX(x)...build();
		return predict(rssis, sensors);
	}
	
	/**
	 * 定位主要实现
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Point predict(List<Rss> rssis, List<Sensor> sensors) {
		//首先过滤掉客户端信号值中过小的信号值
		List<Rss> clientRssis = CommonUtils.rssFilter(rssis);
		//对信号值排序（按mac值排序）,排序后可减小后面指纹匹配的复杂度
		Collections.sort(clientRssis);
		List<Result> result = new ArrayList<>();
		//这里会遍历数据源中所有数据
		while (dataSource.hasNext()) {
			//取得一个坐标上的指纹数据
			Print print = dataSource.next();
			//计算得到距离或相似度值，放入结果中
			result.add(distance(print, clientRssis));
		}
		
		//之所以不用Collections.sort排序的方式找前k个值是由于:找最小k个数只需要k*O(N)的复杂度,
		//当然还有另外种选择排序的算法，后面实现(这是由于一般k值很小, 3到4左右)
		//******************实现选择排序********************
		//而使用Collections.sort排序则需要k*N*log(N)的复杂度
		List<Result> kResult = new ArrayList<>();
		if (this.k < result.size()) {
			for (int i = 0; i < this.k; i++) {
				int idx;
				if (this.distanceMethod.equals("euclidean")) {
					idx = min(result);
				} else {
					idx = max(result);
				}
				kResult.add(result.get(idx));
			}
		} else {
			kResult.addAll(result);
		}
		
		//以均值的形式得到结果值
		return avgResult(kResult);
		//以权重的形式得到计算得到结果值
//		return weightResult(kResult);
		
//		Collections.sort(result, new Comparator<Result>() {
//
//			@Override
//			public int compare(Result o1, Result o2) {
//
//				//欧氏距离是越小越好
//				if (distanceMethod.equals("euclidean")) {
//					return Double.compare(o1.value(), o2.value());
//				} 
//				//其它则是相似度
//				return Double.compare(o2.value(), o1.value());
//			}
//		});
		
	}
	
	/**
	 * 这里实现数据库中相应指纹数据的选择
	 * 我首先实现的是所有指纹数据都匹配
	 * @param print
	 * @param rssis
	 * @return
	 */
	private Result distance(Print print, List<Rss> rssis) {
		
		double sum = 0;
		int len = print.length();
		for (int i = 0; i < len; i++) {
			sum += distance(rssis, print.get(i));
		}
		Result ret = new Result(sum/len, print.point());
		return ret;
	}
	
	/**
	 * 这里的rssis1和rssi2都已经排序（按照mac值排序）
	 * @param rssis1
	 * @param rssis2
	 * @return
	 */
	private double distance(List<Rss> rssis1, List<Rss> rssis2) {
		
		List<Integer> rss1 = new ArrayList<>();
		for (Rss r : rssis1) rss1.add(r.RSS());
		
		List<Integer> rss2 = new ArrayList<>();
		for (Rss r : rssis2) rss2.add(r.RSS());
		
		return distance(rss1, rss2, this.distanceMethod);
	}
	
	/**
	 * 根据传入的字符串使用欧式距离、余弦相似度等
	 * 计算两个rss的距离或相似度
	 * @param rss1
	 * @param rss2  待匹配指纹
	 * @param point 待匹配指纹坐标
	 * @return
	 */
	private double distance (List<Integer> rss1, List<Integer> rss2, String method) {
		
//		Result ret = null;
		if (method.trim().equals("euclidean")) return MathUtils.euclidean(rss1, rss2);
		if (method.trim().equals("cosine"))    return MathUtils.cosine(rss1, rss2);
		return 0;
	}
	
	/**
	 * 求最小值的索引值
	 * @param results
	 * @return 最小值的索引值
	 */
	private int min(List<Result> results) {
		
		int minIdx = 0;
		int len = results.size();
		for (int i = 1; i < len; i++) {
			if (Double.compare(results.get(minIdx).value(), results.get(i).value()) < 0) {
				minIdx = i;
			}
		}
		return minIdx;
	}
	
	/**
	 * 求最大值得索引值
	 * @param results
	 * @return 最大值索引值
	 */
	private int max(List<Result> results) {
		int maxIdx = 0;
		int len = results.size();
		for (int i = 1; i < len; i++) {
			if (Double.compare(results.get(maxIdx).value(), results.get(i).value()) > 0) {
				maxIdx = i;
			}
		}
		return maxIdx;
	}
	
	/**
	 * 对k个结果坐标值求均值
	 * @param results
	 * @return
	 */
	private Point avgResult(List<Result> results) {
		
		int len = results.size();
		int x_sum = 0, y_sum = 0;
		for (Result ret : results) {

			x_sum += ret.point().getX();
			y_sum += ret.point().getY();
		}
		x_sum /= len;
		y_sum /= len;
		return new Point(x_sum, y_sum);
	}
	
	/**
	 * 对k个结果坐标值作权重的方式得到结果定位点
	 * @param results
	 * @return
	 */
	private Point weightResult(List<Result> results) {
		
		return null;
	}
	
	/**
	 * 选择排序
	 * @param results 待排序值
	 * @param k k个最小或最大
	 */
	private void selectSort(List<Result> results, int k) {
		
	}
	
}
